// This is the main DLL file.

#include "stdafx.h"

#include "ComputerEngine.h"

using namespace DbReader;
using namespace System::IO;
using namespace System::Collections::Generic;
using namespace msclr::interop;

namespace ComputerEngine
{
	Engine::Engine()
	{
		InitEngine();

		_pDbReader = NULL;

		m_pSynch = new SEngineSynch();
		m_pCfg = new SEngineData();
		m_pCfg->pReportFuncData = m_pSynch;

		_backgroundThreadFinished = gcnew ManualResetEvent(false);

		_backgroundThread = gcnew Thread(gcnew ThreadStart(this, &Engine::BackgroundThreadWorker));
		_backgroundThread->Priority = System::Threading::ThreadPriority::Lowest;
		_backgroundThread->Start();
	}

	bool Engine::LoadOpeningDatabase(String^ sOpeningPath)
	{
		String^ sFilePath = Path::Combine(sOpeningPath, _openingFile);
		std::string stdFile = marshal_as<std::string>(sFilePath);
	
		return InitOpeningBook(stdFile);
	}

	void Engine::PreloadEndgameDatabase(String^ sEndGamePath, int cacheSizeInMB)
	{
		// convert the path
		pin_ptr<const wchar_t> wchEndGamePath = PtrToStringChars(sEndGamePath);

		// init CDbReader - prepare for pre-loading EGTB database
		m_pCfg->pReader = new CDbReader<EGTB_MAX_PIECES, EGTB_MAX_PIECE, EGTB_MAX_RANK>();

		// set varibles for preloading
		m_pCfg->iEndGameCacheSizeInMB = cacheSizeInMB;
		wcscpy( &(m_pCfg->szEGTBPath[0]), wchEndGamePath);

		::SetEvent(m_pSynch->hEvents[EVENT_LOAD_ENDGAMEDB]);
	}

	void Engine::TerminateComputer()
	{
		::SetEvent(m_pSynch->hEvents[EVENT_TERMINATE]);
		_backgroundThreadFinished->WaitOne();
	}


	void Engine::BackgroundThreadWorker()
	{
		// do initialization
		_bLoadingEndGameDB = false;
		InitEvents(m_pSynch);

		// start the engine thread
		::CreateThread(NULL, 0, EngineThread, m_pCfg, 0, NULL);

		// do waiting for work
		while(true)
		{
			// wait for an event
			DWORD dwResult = ::WaitForMultipleObjects(MAX_EVENTS, m_pSynch->hEvents, FALSE, INFINITE);

			// if some work
			if(dwResult >= WAIT_OBJECT_0 && dwResult < WAIT_OBJECT_0 + MAX_EVENTS)
			{
				// switch work
				dwResult -= WAIT_OBJECT_0;
				switch(dwResult)
				{
				case EVENT_REPORT_PROGRESS:
					{
						// get percents
						::WaitForSingleObject(m_pSynch->hProgressMutex, INFINITE);
						ProgressReportEventArgs^ eProgressArgs = gcnew ProgressReportEventArgs();
						eProgressArgs ->ProgressPercent = m_pSynch->iProgressPercents;
						::ReleaseMutex(m_pSynch->hProgressMutex);
					
						// restore event
						::ResetEvent(m_pSynch->hEvents[EVENT_REPORT_PROGRESS]);
					
						// do report
						ProgressReport(this, eProgressArgs);
					} break;
				case EVENT_LOADING_DB_FINISHED:
					_bLoadingEndGameDB = false;
					::ResetEvent(m_pSynch->hEvents[EVENT_LOADING_DB_FINISHED]);

					BackgroundProcessFinished(this, EventArgs::Empty);
					break;
				case EVENT_TERMINATE:
					::ResetEvent(m_pSynch->hEvents[EVENT_TERMINATE]);

					// check to terminate loading endgame DB
					if(_bLoadingEndGameDB == true)
					{
						SetTerminationFlag(m_pCfg);
						::WaitForSingleObject(m_pSynch->hEvents[EVENT_LOADING_DB_EXIT], INFINITE);
						::ResetEvent(m_pSynch->hEvents[EVENT_LOADING_DB_EXIT]);
						_bLoadingEndGameDB = false;
					}

					// check to terminate engine thread
					::SetEvent(m_pSynch->hEvents[EVENT_ENGINE_TERMINATE]);
					::WaitForSingleObject(m_pSynch->hEvents[EVENT_ENGINE_EXITED], INFINITE);

					// free resources of EGTB 
					if(m_pCfg->pReader != NULL)
					{
						delete m_pCfg->pReader;
						m_pCfg->pReader = NULL;
					}
				

					// finish
					goto Exit;				
				case EVENT_LOADING_DB_EXIT:
					::ResetEvent(m_pSynch->hEvents[EVENT_LOADING_DB_EXIT]);
					break;
				case EVENT_LOAD_ENDGAMEDB:
					{
						_bLoadingEndGameDB = true;

						// report full progress
						ProgressReportEventArgs^ eProgressArgs = gcnew ProgressReportEventArgs();
						eProgressArgs ->ProgressPercent = 100;
						ProgressReport(this, eProgressArgs);					
						
						BackgroundProcessEventArgs^ processArgs = gcnew BackgroundProcessEventArgs();
						processArgs->BackgroundProcessType = eBackgroundProcess::PreloadingDatabase;
						BackgroundProcessInit(this, processArgs);

						// start loading thread
						::CreateThread(NULL, 0, BackgroundDBLoader, m_pCfg, 0, NULL);

						// reset event
						::ResetEvent(m_pSynch->hEvents[EVENT_LOAD_ENDGAMEDB]);
					} break;
				case EVENT_COMPUTE_MOVE:
					::ResetEvent(m_pSynch->hEvents[EVENT_COMPUTE_MOVE]);
					m_pCfg->bEngineReportProgress = _bLoadingEndGameDB != true;

					if(m_pCfg->bEngineReportProgress == true)
					{
						BackgroundProcessEventArgs^ processArgs = gcnew BackgroundProcessEventArgs();
						processArgs->BackgroundProcessType = eBackgroundProcess::Thinking;
						BackgroundProcessInit(this, processArgs);
					}

					// run the engine
					::SetEvent(m_pSynch->hEvents[EVENT_ENGINE_START]);				
					break;
				case EVENT_ENGINE_FINISHED:
					{
						::ResetEvent(m_pSynch->hEvents[EVENT_ENGINE_FINISHED]);

						// report the end
						if(m_pCfg->bEngineReportProgress == true)
						{
							ProgressReportEventArgs^ eProgressArgs = gcnew ProgressReportEventArgs();
							eProgressArgs ->ProgressPercent = 100;
							ProgressReport(this, eProgressArgs);					

							BackgroundProcessFinished(this, EventArgs::Empty);
						}

						// probe the endgame database
						uint8 dtm = 0;

						// create the output
						OutputBitBoard^ obb = nullptr; 
						if(m_pCfg->CheckersType == InternationalCheckers) {
							obb = ConvertMoveBitBoard64(m_pCfg->bbBestMove64);
						}
						else
						{
							obb = ConvertMoveBitBoard(m_pCfg->bbBestMove);
						}
						
						obb->Depth = m_pCfg->iReachedDepth;
						obb->CalmPosDepth = m_pCfg->iReachedCalmPosDepth;
						obb->NumberOfNodes = m_pCfg->iNumberOfNodes;
						obb->NodesPerSecond = m_pCfg->iNodesPerSec;
						if(m_pCfg->iCacheHit && m_pCfg->iCacheMiss)
						{
							obb->CacheHit = (m_pCfg->iCacheHit * 100) / (m_pCfg->iCacheHit + m_pCfg->iCacheMiss);
						}
						else
						{
							obb->CacheHit = 0;
						}

						obb->DTM = dtm;

						// report the move
						MoveComputedEventArgs^ moveArgs = gcnew MoveComputedEventArgs();
						moveArgs->OutputBitBoard = obb;
						MoveComputed(this, moveArgs);
					} break;
				} // switch
			}
		} // while

	Exit:;

		// tidy up
		CloseEvents(m_pSynch);
		_backgroundThreadFinished->Set();
	}

	// Initializes EGTB for the testing viewer purposes
	bool Engine::DBLookUpInit(String^ dbPath)
	{
		pin_ptr<const wchar_t> szEndGamePath = PtrToStringChars(dbPath);

		_pDbReader = new CDbReader<_maxPieces, _maxPiece, _maxRank>();
		_pDbReader->Initialize(256, m_pCfg, szEndGamePath);

		return true;
	}

	// Probes the given position
	eGameResult Engine::DBLookUp(CheckersBoard^ board, bool bWhite, int% dtm)
	{
		// set default values
		dtm = 0;
		eGameResult result = eGameResult::Unknown;
		
		// create bitboard
		BitBoard bb = MakeBitBoard(board);

		// lookup the value
		unsigned char tmpDTM;
		int tmpResult = _pDbReader->Lookup<BitBoard, 4, 31>(&bb, bWhite ? WHITE : BLACK, &tmpDTM);
		
		// convert values
		dtm = tmpDTM;
		switch(tmpResult)
		{
		case WIN:
			result = bWhite ? eGameResult::WhiteWin : eGameResult::BlackWin;
			break;
		case LOSS:
			result = !bWhite ? eGameResult::WhiteWin : eGameResult::BlackWin;
			break;
		case DRAW:
			result = eGameResult::Draw;
			break;
		default:
			result = eGameResult::Unknown;
		}

		return result;
	}

	eCheckersType Engine::GetEGTBDraughtType()
	{
		int draughtType = _pDbReader->GetDraughtsType();
		switch(draughtType)
		{
		case CzechCheckers:
			return eCheckersType::CzechCheckers;
		case EnglishCheckers:
			return eCheckersType::EnglishCheckers;
		case InternationalCheckers:
			return eCheckersType::InternationalCheckers;
		}

		return eCheckersType::Unknown;
	}

	int Engine::GetEGTBMaxPiece()
	{
		return _pDbReader->GetMaxPieces();
	}





	// closes the DB Reader
	void Engine::DBLookUpClose()
	{
		if(_pDbReader != NULL)
		{
			delete _pDbReader;
			_pDbReader = NULL;
		}		
	}

	int Engine::GetOpeningCount()
	{
		return GetOpeningMoveCount();
	}

	array<OutputBitBoard^>^ Engine::GetOpeningByIndex(int index)
	{
		// get opening move by index
		int length = 0;
		BitBoard* bbMove = GetOpeningMoveByIndex(index, length);

		// convert bitboards
		List<OutputBitBoard^>^ list = gcnew List<OutputBitBoard^>();
		for(int i = 0; i < length; i++)
		{
			OutputBitBoard^ outBB = ConvertMoveBitBoard(*(bbMove + i));
			list->Add(outBB);
		}

		return list->ToArray();
	}


	void Engine::ComputeMove(eCheckersType checkersType, CheckersBoard^ board, bool bWhite, EngineSettings^ settings, array<OutputBitBoard^>^ gameHistory)
	{
		_engineSettings = settings;

		// check if game started white player
		bool bFirstWhite = bWhite;
		if(gameHistory->Length % 2 == 0)
			bFirstWhite = ! bFirstWhite;

		BitBoard* bbGameHistory = NULL;
		BitBoard64* bbGameHistory64 = NULL;


		// convert position
		switch(checkersType)
		{
			case eCheckersType::CzechCheckers:
			case eCheckersType::EnglishCheckers:
				// set start position
				m_pCfg->bbStartPosition = MakeBitBoard(board);
				m_pCfg->CheckersType = checkersType == eCheckersType::EnglishCheckers ? EnglishCheckers : CzechCheckers;

				// save history of moves
				// REF
				bbGameHistory = new BitBoard[gameHistory->Length];
				for(int i = 0; i < gameHistory->Length; i++)
					bbGameHistory[i] = ConvertFromOutputBitBoard(gameHistory[i]);
				PushMoveHistory(m_pCfg, bbGameHistory, gameHistory->Length, bFirstWhite);

				break;
			case eCheckersType::InternationalCheckers:
				// set start position
				m_pCfg->bbStartPosition64 = MakeBitBoard64(board);
				m_pCfg->CheckersType = InternationalCheckers;

				// save history of moves
				// REF
				bbGameHistory64 = new BitBoard64[gameHistory->Length];
				for(int i = 0; i < gameHistory->Length; i++)
					bbGameHistory64[i] = ConvertFromOutputBitBoard64(gameHistory[i]);
				PushMoveHistory(m_pCfg, bbGameHistory64, gameHistory->Length, bFirstWhite);

				break;
			default:
				assert(false);
		}

		// set settings
		m_pCfg->bWhiteTurn = bWhite;
		switch(_engineSettings->SearchType)
		{
		case eSearchType::NegaMax:
			m_pCfg->dwSearchType = SEARCH_NEGAMAX;
			break;
		case eSearchType::AlphaBeta:
			m_pCfg->dwSearchType = SEARCH_ALPHABETA;
			break;
		case eSearchType::MTDf:
			m_pCfg->dwSearchType = SEARCH_MTDf;
			break;
		case eSearchType::PVS:
			m_pCfg->dwSearchType = SEARCH_PVS;
			break;
		}
		m_pCfg->dwComputingTime = _engineSettings->MaxSeconds * 1000;
		m_pCfg->dwMaxDepth = _engineSettings->MaxDepth;
		m_pCfg->bUseEndGameDB = _engineSettings->UseEndGameDB;
		m_pCfg->bUseCache = _engineSettings->UseCache;
		m_pCfg->bUseKillerMoves = _engineSettings->UseKillerMoves;
		m_pCfg->bUseHistoryTables = _engineSettings->UseHistoryHeuristics;
		m_pCfg->iCacheSizeMB = _engineSettings->CacheSizeMB;
		m_pCfg->bUsePositionEvaluation = _engineSettings->UsePositionStrategy;

		// signal the thread to compute the move
		::SetEvent(m_pSynch->hEvents[EVENT_COMPUTE_MOVE]);
	}
}
